/*
 * This project provides a number of implementations for indexing and evaluating
 * <i>Continuous Top-k Queries over Text Streams</i>
 * and has been developped as part of the work described in the article:
 * 
 * Vouzoukidou, N., Amann, B., & Christophides, V. (2012, October). 
 * Processing continuous text queries featuring non-homogeneous scoring functions.
 * In Proceedings of the 21st ACM international conference on Information and 
 * knowledge management (pp. 1065-1074). ACM.
 * http://dl.acm.org/citation.cfm?id=2398404
 * 
 * This software is distributed under the GNU General Public License and as an
 * additional condition of usage, please cite the aforementioned article.
 * 
 * Copyright (C) 2012 ICS/FORTH, Greece and 
 *               2012 LIP6, France
 * 
 * Author: Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package gr.forth.ics.continuoustopk.index;

import gr.forth.ics.continuoustopk.Item;
import gr.forth.ics.continuoustopk.Constants;
import gr.forth.ics.continuoustopk.Query;
import gr.forth.ics.continuoustopk.Term;
import java.util.ArrayList;
import java.util.List;

/**
 * Provides a basic implementation of a QueryIndex
 *
 * @author Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 */
public abstract class AbstractQueryIndex implements QueryIndex {
    private List<QueryUpdatedListener> notifiedListeners;
    private List<QueryVisitedListener> visitedListeners;
    private List<ItemMatchedListener> itemMatchedListeners;
    protected UpdateStrategy updateStrategy = UpdateStrategy.update;
    protected final int timestamp;

    protected AbstractQueryIndex(int timestamp) {
        if (!isCompatibleWithDecay()) {
            throw new UnsupportedOperationException(Constants.decay.type()
                    + " decay can't be used with this index.");
        }
        notifiedListeners = new ArrayList<QueryUpdatedListener>();
        visitedListeners = new ArrayList<QueryVisitedListener>();
        itemMatchedListeners = new ArrayList<ItemMatchedListener>();
        this.timestamp = timestamp;
    }
    protected AbstractQueryIndex() {
        this(0);
    }

    @Override
    public void addAll(Iterable<Query> queries) {
        for (Query query : queries) {
            add(query);
        }
    }

    public QueryIndex addListener(QueryUpdatedListener listener) {
        notifiedListeners.add(listener);
        return this;
    }

    public QueryIndex addListener(QueryVisitedListener listener) {
        visitedListeners.add(listener);
        return this;
    }

    public QueryIndex addListener(ItemMatchedListener listener) {
        itemMatchedListeners.add(listener);
        return this;
    }

    private void notifyItemMatchedListenersBefore(Item item) {
        for (ItemMatchedListener listener : itemMatchedListeners) {
            listener.beforeItemMatched(item);
        }
    }

    private void notifyItemMatchedListenersAfter(Item item) {
        for (ItemMatchedListener listener : itemMatchedListeners) {
            listener.afterItemMatched(item);
        }
    }

    /**
     * This method is called <b>before</b> the update of a query with the new item
     * @param query
     * @param item
     * @param term
     * @param skyband
     */
    protected void notifyUpdatedListeners(Query query, Item item, Term term, int skyband) {
        for (QueryUpdatedListener listener : notifiedListeners) {
            listener.onQueryUpdate(query, item, term, skyband);
        }
    }

    /**
     * This method is called <b>before</b> visiting the given query
     * @param query
     * @param term
     * @param bucket
     */
    protected void notifyVisitedListeners(Query query, Item item, Term term, int bucket) {
        for (QueryVisitedListener listener : visitedListeners) {
            listener.onQueryVisited(query, item, term, bucket);
        }
    }

    protected double score(Query query, Item item) {
        return Constants.decay.inverseDecay(Constants.rankingFunction.score(query, item), item.timestamp());
    }

    protected boolean shouldUpdate(Query query, double score) {
        return  score > query.minScore();
    }

    public final void match(Item item) {
        notifyItemMatchedListenersBefore(item);
        matchItem(item);
        notifyItemMatchedListenersAfter(item);
    }

    protected boolean isCompatibleWithDecay() {
        return true;//Constants.decay.type() == DecayFunctionType.noDecay;
    }

    protected boolean match(Query query, Item item, Term term, double score, int index) {
        boolean result = false;
        if (shouldUpdate(query, score)) {
            notifyUpdatedListeners(query, item, term, index);
            beforeUpdatingQueryScore(query, item, score);
            updateStrategy.maybeUpdate(query, item, score);
            afterUpdatingQueryScore(query, item, score);
            result = true;
        }
        notifyVisitedListeners(query, item, term, index);
        return result;
    }

    protected boolean match(Query query, Item item, Term term, int index) {
        double score = Constants.rankingFunction.score(query, item);
        return match(query, item, term, score, index);
    }

    protected abstract void matchItem(Item item);

    protected void beforeUpdatingQueryScore(Query query, Item item, double score) {}
    protected void afterUpdatingQueryScore(Query query, Item item, double score) {}

    public abstract Iterable<Query> queries(Term term);

    public abstract void clear();

    public enum UpdateStrategy {
        doNothing {
            void maybeUpdate(Query query, Item item, double score) {
                // do nothing;
            }
        },
        update {
            void maybeUpdate(Query query, Item item, double score) {
                query.update(item, score);
            }
        };

        abstract void maybeUpdate(Query query, Item item, double score);
    }
}
